﻿using System;
using System.Diagnostics;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace SBPweb.Forge.Communication.WCF
{

    public class PollingDuplexClientService : IPollingDuplexClientService
    {

        #region Private fields

        private IPollingDuplexClientService client;
        private string soapAction;
        private EventHandler<FaultedEventArgs> FaultedEvent;
        
        private static readonly object eventlock = new object();
        private static Dictionary<Type, DataContractSerializer> dictSerializers = new Dictionary<Type, DataContractSerializer>();

        #endregion

        #region Constructor logic

        public PollingDuplexClientService(IPollingDuplexClientService client, string soapAction, string sessionId)
        {
            this.client = client;
            this.soapAction = soapAction;
            this.SessionId = sessionId;
        }

        #endregion

        #region Property accessors

        public bool HasFaulted
        {
            get;
            private set; 
        }

        public string SessionId
        {
            get;
            private set;
        }

        #endregion

        #region Methods

        public IAsyncResult BeginSend(Message message, AsyncCallback callback, object state)
        {
            return client.BeginSend(message, callback, state);
        }

        public void EndSend(IAsyncResult result)
        {
            client.EndSend(result);
        }

        public void Send<TMessageContent>(TMessageContent dataMember)
        {
            Send<TMessageContent, object>(dataMember, null);
        }

        public void Send<TMessageContent, TCommand>(TMessageContent messageContent, TCommand messageCommand)
        {
            Type type = typeof(TMessageContent);
            DataContractSerializer serializer;
            if (dictSerializers.ContainsKey(type))
            {
                serializer = dictSerializers[type];
            }
            else
            {
                serializer = new DataContractSerializer(type);
                dictSerializers.Add(type, serializer);
            }

            Message message = Message.CreateMessage(MessageVersion.Soap11, this.soapAction, messageContent, serializer);
            message.Headers.Add(new MessageHeader<Type>(type).GetUntypedHeader("messageContentType", "forge.SBPweb.hu"));
            if (messageCommand != null)
            {
                message.Headers.Add(new MessageHeader<Type>(typeof(TCommand)).GetUntypedHeader("messageCommandType", "forge.SBPweb.hu"));
                message.Headers.Add(new MessageHeader<TCommand>(messageCommand).GetUntypedHeader("messageCommand", "forge.SBPweb.hu"));
            }

            try
            {
                client.BeginSend(message, OnEndSend, null);
            }
            catch (Exception ex)
            {
                this.HasFaulted = true;

                Debug.WriteLine(ex.ToString());
            }
            finally
            {
                if (this.HasFaulted)
                {
                    OnFaulted(new FaultedEventArgs(this));
                }
            }
        }

        private void OnEndSend(IAsyncResult ar)
        {
            try
            {
                client.EndSend(ar);
            }
            catch (Exception ex)
            {
                this.HasFaulted = true;

                Debug.WriteLine(ex.ToString());
            }
            finally
            {
                if (this.HasFaulted)
                {
                    OnFaulted(new FaultedEventArgs(this));
                }
            }
        }

        public event EventHandler<FaultedEventArgs> Faulted
        {
            add
            {
                lock (eventlock)
                {
                    FaultedEvent += value;
                }
            }
            remove
            {
                lock (eventlock)
                {
                    FaultedEvent -= value;
                }
            }
        }

        private void OnFaulted(FaultedEventArgs e)
        {
            EventHandler<FaultedEventArgs> handler = null;

            lock (eventlock)
            {
                handler = FaultedEvent;

                if (handler == null)
                    return;
            }

            handler(this, e);
        }

        #endregion

        #region Subclasses

        public class FaultedEventArgs : EventArgs
        {
            public new static readonly FaultedEventArgs Empty = new FaultedEventArgs();

            public IPollingDuplexClientService Client
            {
                get;
                set;
            }

            public FaultedEventArgs()
            {
            }

            public FaultedEventArgs(IPollingDuplexClientService client)
            {
                this.Client = client;
            }

        }

        #endregion

    }


}